package cn.mjnxin.iot.gate.coder;

import java.net.InetSocketAddress;
import java.util.List;

import cn.mjnxin.iot.base.domain.ChannelData;
import cn.mjnxin.iot.base.domain.ProtocolConfig;
import cn.mjnxin.iot.base.domain.SocketData;
import cn.mjnxin.iot.base.domain.TerminalCoder;
import cn.mjnxin.iot.base.domain.TerminalRepacker;
import cn.mjnxin.iot.base.log.LogWorker;
import cn.mjnxin.iot.base.util.CommonUtil;
import cn.mjnxin.iot.base.util.MixUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
/**
 * 长度域定长多协议解码器

 * @author CLG 2023年10月10日
 * @version V1.0.0
 */
public class Terminal2GateDecoder extends ByteToMessageDecoder{
	private boolean isBigEndian ;//大小端
	private boolean isLengthIncludeLengthFieldLength ;//长度域长度值是否包含长度域本身长度
	private boolean isLengthIncludeOffsetLength ;//长度域长度值是否包含偏移量域本身长度
	private int	 pid;//协议类型--[0,127]共128种协议
	private int	 matchMode;// 识别模式0:起始匹配串+长度 1:起始识别串+长度 2:相同起止识别串+长度+转义符  3:不同起止识别串+长度+转义符
	private int	 beginMatchLength;//识别串对应占位字节个数
	private int	 endMatchLength;//末尾识别串对应占位字节个数
	private int	 lengthFieldOffset = 0;//包括beginHexVal在内长度域的偏移量(lengthFieldOffset不得小于beginHexVal字节数)
	private int	 lengthFieldLength = 0;//长度域本身占位长度
	private int	 extraDataLenth = 0;//从长度字段到数据域之间非长度字段长度(寻址略过)
	private int	 endExtraDataLenth = 0;//数据域之后末尾非长度字段长度(不包含endMatchHexVal长度)
	private String  beginMatchHexVal = "";//起始识别串
	private String  endMatchHexVal = "";//末尾识别串
	// 日志等级 0：无日志 1：报文长度日志 2：报文内容日志 3：所有日志
	private Integer logLevel;

	private TerminalCoder coder;

	private TerminalRepacker repacker;
	
	/**
	 * 默认起始偏移量为0
	 * @param protocolConfig
	 */
	public Terminal2GateDecoder(ProtocolConfig protocolConfig) {
		this.pid = protocolConfig.getPid();
		this.isBigEndian = protocolConfig.isBigEndian();
		this.matchMode = protocolConfig.getMatchMode();
		this.beginMatchHexVal = protocolConfig.getBeginMatchHexVal();
		this.beginMatchLength = beginMatchHexVal.length() / 2;
		this.endMatchHexVal = protocolConfig.getEndMatchHexVal();
		this.endMatchLength = endMatchHexVal.length() / 2;
		this.lengthFieldOffset = protocolConfig.getLengthFieldOffset();
		this.lengthFieldLength = protocolConfig.getLengthFieldLength();
		this.isLengthIncludeLengthFieldLength = protocolConfig.isLengthIncludeLengthFieldLength();
		this.isLengthIncludeOffsetLength = protocolConfig.isLengthIncludeOffsetLength();
		this.extraDataLenth = protocolConfig.getExtraDataLength();
		this.endExtraDataLenth = protocolConfig.getEndExtraDataLength();
		this.logLevel = protocolConfig.getLogLevel();
		this.coder = protocolConfig.getTerminalCoder();
		this.repacker = protocolConfig.getTerminalRepacker();
	}

	@Override
	protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
		if (coder != null) {
			coder.decode(ctx, in, out);
			return;
		}
		try {
			if (matchMode == 0 || matchMode == 1) {
				decodeP1(ctx, in, out);
			}
			else if(matchMode == 2 || matchMode == 3){
				// 识别串长度不可为空
				if (beginMatchLength > 0 && endMatchLength > 0) {
					decodeP2(ctx, in, out);
				}
				else {
					LogWorker.logForce("报文匹配模式不支持");
				}
			}
			else {
				LogWorker.logForce("报文匹配模式不支持");
			}
		}
		catch(Exception e) {
			if (e instanceof IndexOutOfBoundsException) {
				//防止包探测
				ctx.channel().close();
			}
			throw e;
		}
	}

	// 只有起始识别串时，需要严格校验长度信息，起始识别串可以为空
	private void decodeP1(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
		//需要优化读取策略，头部标记，尾部标记，有头部标记时，在查标记时要遍历
		int baseLen = lengthFieldOffset + lengthFieldLength + extraDataLenth + endExtraDataLenth;// + beginMatchLength + endMatchLength;

		if(in.readableBytes() < baseLen){
			return;
		}
		
		while (true) {
			int readableBytes = in.readableBytes();//重新获取可读字段
			if(readableBytes < baseLen){
				break;
			}
			// 获取包头开始的index
			int beginReader = in.readerIndex();//重新获取开始的index
			if (beginMatchLength > 0) {
				byte[] base = new byte[baseLen];
				in.readBytes(base, 0, baseLen);
				String baseHex = MixUtil.bytes2hex(base);
				if (matchMode == 0) {
					boolean found = true;
					int index = 0;
					do {
						for (int i = index, len = beginMatchLength + beginMatchLength + index; i < len; i++) {
							char c = beginMatchHexVal.charAt(i);
							if (c != '?') {
								if (c != baseHex.charAt(i)) {
									found = false;
								}
							}
						}
						
						if (!found) {
							index++;
						}
					} while(found == false);
					if (!found) { // 预读的一段无法识别，丢弃index部分
						System.out.print(baseHex.substring(0, beginMatchLength + beginMatchLength));
						in.readerIndex(beginReader + index / 2);
						readableBytes = in.readableBytes();//重新获取可读字段
						continue;
					}
					else { // 找到识别串
						beginReader = beginReader + index;
						in.readerIndex(beginReader);
						readableBytes = in.readableBytes();//重新获取可读字段
					}
				}
				else {
					int index = baseHex.indexOf(beginMatchHexVal);
					if (index < 0) { // 预读的一段无法识别，丢弃beginMatchLength部分
						System.out.print(baseHex.substring(0, beginMatchLength + beginMatchLength));
						in.readerIndex(beginReader + beginMatchLength);
						readableBytes = in.readableBytes();//重新获取可读字段
//						System.out.println(baseHex);
						continue;
					}
					else { // 找到识别串
						beginReader = beginReader + index /2;
						in.readerIndex(beginReader);
						readableBytes = in.readableBytes();//重新获取可读字段
					}
				}
			
				if(readableBytes < baseLen){
					//报文不完整 还原
					in.readerIndex(beginReader);
					break;
				}
			}
			
			ByteBuf byteBuf = CommonUtil.getByteBuf();
			
			if (lengthFieldOffset > 0) {
				for(int i = 0 ; i < lengthFieldOffset ; i++){
					byteBuf.writeByte(in.readByte());
				}
			}
			
			//处理长度域
			int lenReader = in.readerIndex();
			int lenVal = 0;//data域长度
			switch (lengthFieldLength) {
			case 1:
				lenVal = (in.readByte() & 0x000000FF);
				break;
			case 2:
				if(isBigEndian){
					lenVal = in.readShort();
				}else{
					lenVal = in.readShortLE();
				}
				break;
			case 4:
				if(isBigEndian){
					lenVal = in.readInt();
				}else{
					lenVal = in.readIntLE();
				}
				break;
			default:
				CommonUtil.releaseByteBuf(byteBuf);
				break;
			}
			in.readerIndex(lenReader);
			in.readBytes(byteBuf, lengthFieldLength);
			if(isLengthIncludeLengthFieldLength){
				lenVal = lenVal - lengthFieldLength;
			}
			if(isLengthIncludeOffsetLength){
				lenVal = lenVal - lengthFieldOffset;
			}

			if(in.readableBytes() >= (lenVal + extraDataLenth + endExtraDataLenth)  && lenVal >= 0){
				byteBuf.writeBytes(in, lenVal + extraDataLenth + endExtraDataLenth);
				
				Channel channel = ctx.channel();
				InetSocketAddress insocket = (InetSocketAddress)channel.remoteAddress();
				String ipAddress = CommonUtil.getIpAddress(insocket);
				if (repacker != null) {
					byteBuf = repacker.repackFromTerminal(this.pid, ipAddress, byteBuf);
				}
				SocketData data = new SocketData(byteBuf);
				data.setPid(this.pid);
				CommonUtil.recieveCount.addAndGet(1);
				LogWorker.logForce(String.format("%d|T[%s]->G=", this.pid, ipAddress), data.getByteBuf());
				ChannelData channelData =  new ChannelData(ipAddress, this.pid, this.logLevel, data);
				out.add(channelData);
				continue;
			}else{
				//报文不完整 还原
				in.readerIndex(beginReader);
				CommonUtil.releaseByteBuf(byteBuf);
				break;
			}
		}
	}

	//起始识别串的识别模式，识别串不能为空，不需要校验长度信息
	private void decodeP2(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
		//需要优化读取策略，头部标记，尾部标记，有头部标记时，在查标记时要遍历
		int baseLen = lengthFieldOffset + lengthFieldLength + extraDataLenth + endExtraDataLenth;

		if((in.readableBytes() <  beginMatchLength)){
			return;
		}
		
		while (true) {
			int readableBytes = in.readableBytes();//重新获取可读字段
			if((readableBytes < baseLen + endMatchLength)){
				break;
			}
			int beginReader = in.readerIndex();//重新获取开始的index

			byte[] base = new byte[baseLen];
			in.readBytes(base, 0, baseLen);
			String baseHex = MixUtil.bytes2hex(base);
			int index = baseHex.indexOf(beginMatchHexVal);
			if (index < 0) { // 预读的一段无法识别，丢弃
				System.out.println(baseHex);
				continue;
			}
			else { // 找到识别串
				beginReader = beginReader + index /2; //识别串起始位
				//越过一段字节再查找尾部识别串
				in.readerIndex(beginReader + lengthFieldOffset + lengthFieldLength + extraDataLenth + endExtraDataLenth);
				readableBytes = in.readableBytes();//重新获取可读字段
			}
			
			byte[] allBuf = null;
			while (readableBytes >= endMatchLength) {
				int endReader = in.readerIndex();//重新获取结束的index
				byte[] end = new byte[endMatchLength];
				in.readBytes(end, 0, endMatchLength);
				String endHex = MixUtil.bytes2hex(end);
				if (!endHex.equals(endMatchHexVal)) { // 预读的一段无法识别
					readableBytes = in.readableBytes();//重新获取可读字段
					continue;
				}
				else { // 找到识别串
					endReader = in.readerIndex();//重新获取结束的index
				}
				in.readerIndex(beginReader);
				allBuf = new byte[endReader - beginReader];
				in.readBytes(allBuf, 0, allBuf.length);
				break;
			}
			if (allBuf != null) {
				Channel channel = ctx.channel();
				InetSocketAddress insocket = (InetSocketAddress)channel.remoteAddress();
				String ipAddress = CommonUtil.getIpAddress(insocket);
				SocketData data = new SocketData(Unpooled.wrappedBuffer(allBuf));
				data.setPid(this.pid);
				CommonUtil.recieveCount.addAndGet(1);
				if (logLevel == 1) {
					LogWorker.logForce(String.format("%d|T[%s]->G=%d Bytes", this.pid, ipAddress, allBuf.length));
				}
				if (logLevel >= 2) {
					LogWorker.logForce(String.format("%d|T[%s]->G=", this.pid, ipAddress), allBuf);
				}
				ChannelData channelData =  new ChannelData(ipAddress, this.pid, this.logLevel, data);
				out.add(channelData);
				continue;
			}
			else {
				//报文不完整 还原
				in.readerIndex(beginReader);
				break;
			}
		}
	}

	public String getBeginHexVal() {
		return beginMatchHexVal;
	}
}
